/*
Copyright (C) 2011 Ing. Marek Hubal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "core.h"
#include "IBitmap2.h"

#pragma warning (disable: 4996)

#pragma pack(1)
typedef struct {
	BYTE Magic1;
	BYTE Magic2;
	UINT32 FileSize;
	UINT16 Reserver1;
	UINT16 Reserver2;
	UINT32 BmpOffset;
	UINT32 HeaderSize;
	INT32 Width;
	INT32 Height;
	UINT16 NPlanes;
	UINT16 BitsPP;
	UINT32 CompressType;
	UINT32 BmpByteSize;
	INT32 HRes;
	INT32 VRes;
	UINT32 NumOfColors;
	UINT32 NumOfImpColors;
} BMP_FILE_HEADER, *PBMP_FILE_HEADER;
#pragma pack()

const BYTE MASK_TABLE_1[] = {0xff, 0x80, 0xa0, 0xe0, 0xf0, 0xf8, 0xfa, 0xfe};
const BYTE COUNT_TABLE_1[] = {
				0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,
				1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
				1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
				1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
				2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
				3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
				3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
				4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
							};

// ................................................................................................
// Konstruktor
// ................................................................................................
IBitmap2::IBitmap2(void) {
	RawData = NULL; // Inicializacia bufrov
	Rows = NULL;
	Clear(); // Inicializujem premenne
}
// ................................................................................................
// ................................................................................................
// Destruktor
// ................................................................................................
IBitmap2::~IBitmap2(void) {
	Clear();
}
// ................................................................................................
// ................................................................................................
// Uvolnenie objektu z pamate
// ................................................................................................
void __fastcall IBitmap2::Clear(void) {

	if (RawData != NULL) delete RawData; // Zrusim bufre
	if (Rows != NULL) delete Rows;

	RawData = NULL; // Inicializacia bufrov
	Rows = NULL;

	Width = Height = 0; // Inicializacia premennych
	RowSize = 0;
	Pallete[0] = 0xffffff; Pallete[1] = 0;
	RawDataSize = 0;
	Count0 = Count1 = 0;
	ValidRowSize = 0;
	LastRowByteMask = 0;
}
// ................................................................................................
// ................................................................................................
// Nacitanie obsahu z BMP suboru
// ................................................................................................
INT32 __fastcall IBitmap2::LoadFromBMP(CONST_PCHAR pFileName) {
	Clear(); // Uvolnim aktualny obsah

	FILE *F = fopen(pFileName, "rb"); // Otvorim subor
	if (F == NULL) return BITMAP2_IMAGE_ERROR_OPEN_FILE;

	BMP_FILE_HEADER Header;
	if (fread(&Header, sizeof(BMP_FILE_HEADER), 1, F) != 1) { // Nacitam hlavicku
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_READ_FILE;
	}

	if ((Header.Magic1 != 'B') && (Header.Magic2 != 'M')) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_FILE_HEADER_CORRUPTED;
	}
	if ((Header.NPlanes != 1) || (Header.HeaderSize != 40)) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_FILE_HEADER_CORRUPTED;
	}
	if ((Header.Width < 1) || (Header.Height < 1)) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_FILE_HEADER_CORRUPTED;
	}	
	if (Header.BitsPP != 1) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_UNSUPORTED_BITS_PER_PIXEL;
	}
	if (Header.CompressType != 0) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_UNSUPORTED_COMPRESSION;
	}

	fseek(F, 14 + Header.HeaderSize, 0L); // Nastavim sa na paletu
	if (fread(Pallete, sizeof(COLORREF), 2, F) != 2) { // Nacitam paletu
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_READ_FILE;
	}

	fseek(F, Header.BmpOffset, 0L); // Nastavim sa na pixely

	Width = Header.Width; Height = Header.Height; // Ulozim rozmery
	RowSize = ALIGN_32(Width); RowSize >>= 3;
	RawDataSize = Height * RowSize; // Urcim velkost dat

	ValidRowSize = Width >> 3; // Urcim pocet platnych bajtov v riadku a bitovu masku posledneho
	if (ValidRowSize << 3 != Width) {
		LastRowByteMask = MASK_TABLE_1[Width - (ValidRowSize << 3)];
		ValidRowSize++;
	}
	else LastRowByteMask = 0xff;

	if (RawDataSize != Header.BmpByteSize) {
		fclose(F); // Zatvorim subor
		return BITMAP2_IMAGE_ERROR_FILE_HEADER_CORRUPTED;
	}

	RawData = new BYTE[RawDataSize]; // Alokujem pamat
	Rows = new PBYTE[Height]; // Alokujem pole pre zaciatky riadkov
	Count0 = Count1 = 0; // Inicializujem pocitadla

	PBYTE CurrentRow = RawData + (Height - 1) * RowSize; // Inicializujem smernik na posledny riadok
	INT32 C = ValidRowSize - 1; // Pomocne pocitadlo
	INT32 C1;

	for (INT32 i = Height - 1; i >= 0; i--) {
		if (fread(CurrentRow, 1, RowSize, F) != RowSize) {
			fclose(F); // Zatvorim subor
			return BITMAP2_IMAGE_ERROR_READ_FILE;
		}
		Rows[i] = CurrentRow; // Ulozim zaciatok riadku
		
		CurrentRow[ValidRowSize - 1] &= LastRowByteMask; // Nulujem nadbytocne bity
		for (INT32 j = ValidRowSize; j < RowSize; j++) CurrentRow[j] = 0;

		for (INT32 j = 0; j < C; j++) { // Pocitam pocty jednotiek a nul
			C1 = COUNT_TABLE_1[CurrentRow[j]];
			Count1 += C1; Count0 += 8 - C1;
		}
		
		C1 = COUNT_TABLE_1[CurrentRow[C] & LastRowByteMask]; // Posledny platny byte
		Count1 += C1; Count0 += 8 - C1;

		CurrentRow -= RowSize; // Predchadzajuci riadok
	}

	if (Count1 > Count0) { // Treba vymenit bity? 0 - pozadie, 1 - popredie
		COLORREF CC = Pallete[0]; Pallete[0] = Pallete[1]; Pallete[1] = CC; // Vymenim farby
		
		CurrentRow = RawData;
		for (INT32 i = 0; i < Height; i++) {
			for (INT32 j = 0; j < C; j++) CurrentRow[j] ^= 0xff; // Preklopim bity
			CurrentRow[C] ^= LastRowByteMask & 0xff; // Posledny platny byte
			CurrentRow += RowSize; // Nasledujuci riadok
		}
		CC = Count1; Count1 = Count0; Count0 = CC;
	}

	fclose(F); // Zatvorim subor
	return 0; // Vsetko OK
}
// ................................................................................................
// ................................................................................................
// Ulozenie obsahu do BMP suboru
// ................................................................................................
INT32 __fastcall IBitmap2::SaveToBMP(CONST_PCHAR pFileName) {
	BMP_FILE_HEADER Header;
	
	Header.Magic1 = 'B'; Header.Magic2 = 'M'; // Vyplnim strukturu
	Header.Reserver1 = Header.Reserver2 = 0;
	Header.BitsPP = 1; Header.BmpByteSize = RawDataSize; Header.BmpOffset = sizeof(BMP_FILE_HEADER) + 2*sizeof(COLORREF);
	Header.CompressType = 0; Header.HeaderSize = 40; 
	Header.Height = Height; Header.Width = Width; 
	Header.HRes = Header.VRes = 0; Header.NPlanes = 1; Header.NumOfColors = 2; Header.NumOfImpColors = 0;
	Header.FileSize = Header.BmpOffset + RawDataSize;

	FILE *F = fopen(pFileName, "wb+"); // Vytvorim subor
	if (F == NULL) return BITMAP2_IMAGE_ERROR_CREATE_FILE;

	if (fwrite(&Header, sizeof(BMP_FILE_HEADER), 1, F) != 1) {
		fclose(F); // Uzatvorim subor
		return BITMAP2_IMAGE_ERROR_WRITE_FILE;
	}
	if (fwrite(Pallete, sizeof(COLORREF), 2, F) != 2) {
		fclose(F); // Uzatvorim subor
		return BITMAP2_IMAGE_ERROR_WRITE_FILE;
	}

	PBYTE CurrentRow = RawData + (Height - 1) * RowSize; // Inicializujem smernik na posledny riadok
	for (INT32 i = 0; i < Height; i++) {
		if (fwrite(CurrentRow, 1, RowSize, F) != RowSize) {
			fclose(F); // Zatvorim subor
			return BITMAP2_IMAGE_ERROR_WRITE_FILE;
		}
		CurrentRow -= RowSize; // Predchadzajuci riadok
	}

	fclose(F); // Uzatvorim subor
	return 0; // Vsetko OK
}
// ................................................................................................
// ................................................................................................
// Ziskanie hodnoty pixela na zadanej pozicii
// ................................................................................................
bool __fastcall IBitmap2::GetPixel(INT32 pX, INT32 pY) {
	if ((pX < 0) || (pX >= Width)) return false;
	if ((pY < 0) || (pY >= Height)) return false;
	BYTE M = 0x80; M >>= pX & 0x7;
	return (*(Rows[pY] + (pX >> 3)) & M) != 0;
}
// ................................................................................................
// ................................................................................................
// Nastavenie hodnoty pixela na zadanej pozicii
// ................................................................................................
void __fastcall IBitmap2::SetPixel(INT32 pX, INT32 pY, bool pValue) {
	if ((pX < 0) || (pX >= Width)) return;
	if ((pY < 0) || (pY >= Height)) return;
	BYTE M = 0x80; M >>= pX & 0x7;
	if (pValue) *(Rows[pY] + (pX >> 3)) |= M;
	else *(Rows[pY] + (pX >> 3)) &= (~M);
}
// ................................................................................................
// ................................................................................................
// Vytvorenie kopie aktualneho obrazku
// ................................................................................................
IBitmap2* __fastcall IBitmap2::Clone(void) {
	IBitmap2 *Result = new IBitmap2();
	if (RawData == NULL) return Result; // Je obsah prazdny?

	Result->Width = Width; // Skopirujem premenne
	Result->Height = Height; 
	Result->RowSize = RowSize;
	Result->Pallete[0] = Pallete[0];
	Result->Pallete[1] = Pallete[1];
	Result->RawDataSize = RawDataSize;
	Result->Count0 = Count0; 
	Result->Count1 = Count1;
	Result->ValidRowSize = ValidRowSize;
	Result->LastRowByteMask = LastRowByteMask;

	Result->RawData = new BYTE[RawDataSize]; memcpy(Result->RawData, RawData, RawDataSize); // Skopirujem raw data
	Result->Rows = new PBYTE[Height];
	PBYTE P = Result->RawData;
	for (INT32 i = 0; i < Height; i++) { // Vytvorim mapu riadkov
		Result->Rows[i] = P; P += RowSize;
	}

	return Result;
}
// ................................................................................................
// ................................................................................................
// Vytvorenie prazdneho obrazku
// ................................................................................................
void __fastcall IBitmap2::Create(INT32 pWidth, INT32 pHeight) {
	Clear(); // Uvolnim aktualny obsah

	Width = pWidth; // Ulozim rozmery
	Height = pHeight; 

	RowSize = ALIGN_32(Width); RowSize >>= 3;
	RawDataSize = Height * RowSize; // Urcim velkost dat

	ValidRowSize = Width >> 3; // Urcim pocet platnych bajtov v riadku a bitovu masku posledneho
	if (ValidRowSize << 3 != Width) {
		LastRowByteMask = MASK_TABLE_1[Width - (ValidRowSize << 3)];
		ValidRowSize++;
	}
	else LastRowByteMask = 0xff;
	Count0 = Height * Width; Count1 = 0;

	RawData = new BYTE[RawDataSize]; memset(RawData, 0, RawDataSize); // Vytvorim a inicializujem raw data
	Rows = new PBYTE[Height];
	PBYTE P = RawData;
	for (INT32 i = 0; i < Height; i++) { // Vytvorim mapu riadkov
		Rows[i] = P; P += RowSize;
	}
}
// ................................................................................................
